package com.ease.component.managers {	
   
   import com.ease.component.controls.dashboard.DragHighlight;
   import com.ease.component.controls.dashboard.Pod;
   import com.ease.component.events.LayoutChangeEvent;
   import com.ease.component.events.PodStateChangeEvent;
   
   import flash.events.EventDispatcher;
   import flash.events.MouseEvent;
   import flash.geom.Point;
   import flash.geom.Rectangle;
   
   import mx.containers.Canvas;
   import mx.core.Application;
   import mx.core.FlexGlobals;
   import mx.effects.Move;
   import mx.effects.Parallel;
   import mx.effects.Resize;
   import mx.effects.easing.Exponential;
   import mx.events.DragEvent;
   import mx.events.ResizeEvent;
   
   // Dispatched whenever the layout changes.
   [Event(name="update", type="com.ease.component.events.LayoutChangeEvent")]
   
   public class PodLayoutManager extends EventDispatcher {
   	public var id:String;
   	public var items:Array = new Array();					// Stores the pods which are not minimized.
   	public var minimizedItems:Array = new Array();			// Stores the minimized pods.
   	public var maximizedPod:Pod;
   	
   	private var dragHighlightItems:Array = new Array();		// Stores the highlight items used to designate a drop area.
   	private var gridPoints:Array = new Array();				// Stores the x,y of each pod in the grid.
   	
   	private var currentDragPod:Pod;							// The current pod which the user is dragging.
   	private var currentVisibleHighlight:DragHighlight;		// The current highlight that is visible while dragging.
   	private var currentDropIndex:Number;					// The index of where to drop the pod while dragging.
   	private var currentDragPodMove:Move;					// The move effect used to transition the pod after it is released from dragging.
   	
   	private var _container:Canvas;							// The container which holds all of the pods.
   	
   	private var parallel:Parallel;							// The main effect container.
   	private var maximizeParallel:Parallel;
   	
   	private var itemWidth:Number;							// Pod width.
   	private var itemHeight:Number;							// Pod height.
   	
   	private static const POD_GAP:Number = 10;				// The vertical and horizontal gap between pods.
   	private static const TASKBAR_HEIGHT:Number = 25;		// The height of the area for minimized pods.
   	private static const TASKBAR_HORIZONTAL_GAP:Number = 5; // The horizontal gap between minimized pods.
   	private static const TASKBAR_ITEM_WIDTH:Number = 150;	// The preferred minimized pod width if there is available space.
   	private static const TASKBAR_PADDING_TOP:Number = 10;	// The gap between the taskbar and the bottom of the last row of pods.
   	private static const PADDING_RIGHT:Number = 5;			// The right padding within the container when laying out pods.
   	
   	// Removes null items from the items array.
   	// This should be called only once after all of the items have been added.
   	// Null items will be present if a pod was saved at an index that is no longer valid
   	// because the number of pods has been reduced in the XML.
   	public function removeNullItems():void {
   		var a:Array = new Array();
   		var len:Number = items.length;
   		for (var i:Number = 0; i < len; i++) {
   			if (items[i] != null)
   				a.push(items[i]);
   		}
   		items = a;
   		_container.addEventListener(ResizeEvent.RESIZE, updateLayout);
   	}
   	
   	// Sets the canvas which will hold the pods.
   	public function set container(canvas:Canvas):void {
   		_container = canvas;
   	}
   	
   	public function get container():Canvas {
   		return _container;
   	}
   	
   	public function addMinimizedItemAt(pod:Pod, index:Number):void {	
   		if (index == -1)
   			index = minimizedItems.length;
   		
   		pod.minimize();
   		
   		minimizedItems[index] = pod;
   		initItem(pod);
   	}
   	
   	public function addItemAt(pod:Pod, index:Number, maximized:Boolean):void {	
   		if (maximized) {
   			maximizedPod = pod;
   			pod.maximize();
   		}
   		
   		items[index] = pod;
   		initItem(pod);
   	}
   	
   	private function initItem(pod:Pod):void {
   		container.addChild(pod);
   		
   		pod.addEventListener(DragEvent.DRAG_START, onDragStartPod);
   		pod.addEventListener(DragEvent.DRAG_COMPLETE, onDragCompletePod);
   		pod.addEventListener(PodStateChangeEvent.MAXIMIZE, onMaximizePod);
   		pod.addEventListener(PodStateChangeEvent.MINIMIZE, onMinimizePod);
   		pod.addEventListener(PodStateChangeEvent.RESTORE, onRestorePod);
   		
   		// Add a highlight for each pod. Used to show a drop target box.
   		var dragHighlight:DragHighlight = new DragHighlight();
   		dragHighlight.visible = false;
   		dragHighlightItems.push(dragHighlight);
   		container.addChild(dragHighlight);
   	}
   	
   	// Pod has been maximized.
   	private function onMaximizePod(e:PodStateChangeEvent):void {
   		var pod:Pod = Pod(e.currentTarget);
   		maximizeParallel = new Parallel();
   		maximizeParallel.duration = 1000;
   		addResizeAndMoveToParallel(pod, maximizeParallel, availablePodWidth, availableMaximizedPodHeight, 0, 0);
   		maximizeParallel.play();
   		
   		maximizedPod = pod;
   		dispatchEvent(new LayoutChangeEvent(LayoutChangeEvent.UPDATE));
   	}
   	
   	// Pod has been minimized
   	private function onMinimizePod(e:PodStateChangeEvent):void {
   		if (maximizeParallel != null && maximizeParallel.isPlaying)
   			maximizeParallel.pause();
   		
   		var pod:Pod = Pod(e.currentTarget);
   		items.splice(pod.index, 1);
   		
   		// Pod was previously maximized so there isn't a minimized pod anymore.
   		if (pod.windowState == Pod.WINDOW_STATE_MAXIMIZED)
   			maximizedPod = null;
   				
   		minimizedItems.push(pod);
   		
   		updateLayout(true);
   		
   		dispatchEvent(new LayoutChangeEvent(LayoutChangeEvent.UPDATE));
   	}
   	
   	// Pod has been restored.
   	private function onRestorePod(e:PodStateChangeEvent):void {
   		var pod:Pod = Pod(e.currentTarget);
   		if (pod.windowState == Pod.WINDOW_STATE_MAXIMIZED) {
   			if (maximizeParallel != null && maximizeParallel.isPlaying)
   				maximizeParallel.pause();
   			
   			maximizedPod = null;
   			maximizeParallel = new Parallel();
   			var point:Point = Point(gridPoints[pod.index]);
   			addResizeAndMoveToParallel(pod, maximizeParallel, itemWidth, itemHeight, point.x, point.y);
   			maximizeParallel.play();
   		}
   		else if (pod.windowState == Pod.WINDOW_STATE_MINIMIZED) {
   			var len:Number = minimizedItems.length;
   			for (var i:Number = 0; i < len; i++) {
   				// Remove the minimized window from the minimized items.
   				if (minimizedItems[i] == pod) {
   					minimizedItems.splice(i, 1);
   					break;
   				}
   			}
   			
   			if (pod.index < (items.length - 1)) // The pod index is within the range of the number of pods.
   				items.splice(pod.index, 0, pod);
   			else								// Out of range so add pod at the end.
   				items.push(pod);
   				
   			updateLayout(true);
   		}
   		dispatchEvent(new LayoutChangeEvent(LayoutChangeEvent.UPDATE));
   	}
   	
   	private function onDragStartPod(e:DragEvent):void {
   		currentDragPod = Pod(e.currentTarget);
   		var len:Number = items.length;
   		for (var i:Number = 0; i < len; i++) {
   			if (Pod(items[i]) == currentDragPod) {
   				currentDropIndex = i;
   				break;
   			}
   		}
   		
   		FlexGlobals.topLevelApplication.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
   	}
   	
   	private function onDragCompletePod(e:DragEvent):void {
   		FlexGlobals.topLevelApplication.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
   		
   		if (currentVisibleHighlight != null)
   			currentVisibleHighlight.visible = false;
   		
   		// The x/y will not change if a user clicked on a header without dragging. In that case, we want to toggle the window state.
   		var point:Point = Point(gridPoints[currentDropIndex]);
   		if (point.x != currentDragPod.x || point.y != currentDragPod.y) {
   			currentDragPodMove = new Move(currentDragPod);
   			currentDragPodMove.easingFunction = Exponential.easeOut;
   			currentDragPodMove.xTo = point.x;
   			currentDragPodMove.yTo = point.y;
   			currentDragPodMove.play();
   			
   			dispatchEvent(new LayoutChangeEvent(LayoutChangeEvent.UPDATE));
   		}
   	}
   	
   	// Handles the live resorting of pods as one is dragged.
   	private function onMouseMove(e:MouseEvent):void {
   		var len:Number = items.length;	// Use the items since we can have more pods than highlights when a pod(s) is minimized.
   		var dragHighlightItem:DragHighlight;
   		var overlapArea:Number = 0; 	// Keeps track of the amount (w *h) of overlap between rectangles.
   		var dragPodRect:Rectangle = new Rectangle(currentDragPod.x, currentDragPod.y, currentDragPod.width, currentDragPod.height);
   		var dropIndex:Number = -1;		// The new drop index. This will create a range from currentDropIndex to dropIndex for transtions below.
   		
   		// Loop through the highlights and figure out which one has the greatest amount of overlap with the pod that is being dragged.
   		// The highlight with the max overlap will be the drop index.
   		for (var i:Number = 0; i < len; i++) {
   			dragHighlightItem = DragHighlight(dragHighlightItems[i]);
   			dragHighlightItem.visible = false;
   			if (currentDragPod.hitTestObject(dragHighlightItem)) {
   				var dragHighlightItemRect:Rectangle = new Rectangle(dragHighlightItem.x, dragHighlightItem.y, dragHighlightItem.width, dragHighlightItem.height);
   				var intersection:Rectangle = dragHighlightItemRect.intersection(dragPodRect);
   				if ((intersection.width * intersection.height) > overlapArea) {
   					currentVisibleHighlight = dragHighlightItem;
   					overlapArea = intersection.width * intersection.height;
   					dropIndex = i;
   				}
   			}
   		}
   		
   		if (currentDropIndex != dropIndex) {
   			if (dropIndex == -1) // User is not over a highlight.
   				dropIndex = currentDropIndex;
   			
   			if (currentDragPodMove != null && currentDragPodMove.isPlaying)
   				currentDragPodMove.pause();
   			
   			if (parallel != null && parallel.isPlaying)
   				parallel.pause();
   			
   			parallel = new Parallel();
   			parallel.duration = 1000;
   			
   			var a:Array = new Array(); // Used to re-order the items array.
   			a[dropIndex] = currentDragPod;
   			currentDragPod.index = dropIndex;
   			
   			for (i = 0; i < len; i++) {
   				var targetX:Number;
   				var targetY:Number;
   				var point:Point;
   				var pod:Pod = Pod(items[i]);
   				
   				var index:Number;
   				if (i != currentDropIndex) {
   					// Find the index to determine the lookup in gridPoints.
   					if ((i < currentDropIndex && i < dropIndex) ||
   						(i > currentDropIndex && i > dropIndex)) // Below or above the range of dragging.
   						index = i;
   					else if (i > currentDropIndex && i <= dropIndex) // Drag forwards
   						index = i - 1;
   					else if (i < currentDropIndex && i >= dropIndex) // Drag backwards
   						index = i + 1;
   					else
   						index = i;
   					
   					a[index] = pod;
   					pod.index = index;
   					
   					point = Point(gridPoints[index]); // Get the x,y coord from the grid.
   					
   					targetX = point.x;
   					targetY = point.y;
   					
   					if (targetX != pod.x || targetY != pod.y) {
   						var move:Move = new Move(pod);
   						move.easingFunction = Exponential.easeOut;
   						move.xTo = targetX;
   						move.yTo = targetY;
   						parallel.addChild(move);
   					}
   				}
   			}
   			
   			if (parallel.children.length > 0)
   				parallel.play();
   		
   			currentDropIndex = dropIndex;
   			
   			// Reassign the items array so the new order is reflected.
   			items = a;
   		}
   		
   		currentVisibleHighlight.visible = true;
   	}
   	
   	// Lays out the pods, minimized pods and drag highlight items.
   	public function updateLayout(tween:Boolean=true):void {
   		var len:Number = items.length;
   		var sqrt:Number = Math.floor(Math.sqrt(len));
   		var numCols:Number = Math.ceil(len / sqrt);
   		var numRows:Number = Math.ceil(len / numCols);
   		var col:Number = 0;
   		var row:Number = 0;
   		var pod:Pod;
   		itemWidth = Math.round(availablePodWidth / numCols - ((POD_GAP * (numCols - 1)) / numCols));
   		itemHeight = Math.round(availablePodHeight / numRows - ((POD_GAP * (numRows - 1)) / numRows));
   		
   		if (parallel != null && parallel.isPlaying)
   			parallel.pause();
   		
   		if (tween) {
   			parallel = new Parallel();
   			parallel.duration = 1000;
   		}
   		
   		// Layout the pods.
   		for (var i:Number = 0; i < len; i++) {			
   			if(i % numCols == 0 && i > 0) {
   				row++;
   				col = 0;
   			}
   			else if(i > 0) {
   				col++;
   			}
   			
   			var targetX:Number = col * itemWidth;
   			var targetY:Number = row * itemHeight;
   			
   			if(col > 0) 
   				targetX += POD_GAP * col;
   			if(row > 0) 
   				targetY += POD_GAP * row;
   				
   			targetX = Math.round(targetX);
   			targetY = Math.round(targetY);
   			
   			pod = items[i];
   			if (pod.windowState == Pod.WINDOW_STATE_MAXIMIZED) {
   				if (tween) {
   					addResizeAndMoveToParallel(pod, parallel, availablePodWidth, availableMaximizedPodHeight, 0, 0);
   				}
   				else {
   					pod.width = availablePodWidth;
   					pod.height = availableMaximizedPodHeight;
   				}
   				
   				// Move the pod to the top of the z-index. It will not be at the top if we are coming from a saved state
   				// and the pod is not the last one.
   				container.setChildIndex(pod, container.numChildren - 1);
   			}
   			else {
   				if (tween) {
   					addResizeAndMoveToParallel(pod, parallel, itemWidth, itemHeight, targetX, targetY);
   				}
   				else {
   					pod.width = itemWidth;
   					pod.height = itemHeight;
   					pod.x = targetX;
   					pod.y = targetY;
   				}
   			}
   				
   			pod.index = i;
   			
   			gridPoints[i] = new Point(targetX, targetY);
   		}
   		
   		// Layout the minimized items.
   		len = minimizedItems.length;
   		if (len > 0) {
   			// Check to see if all of the minimized items will be too wide.
   			var totalMinimizedItemWidth:Number = len * TASKBAR_ITEM_WIDTH + (len -1) * TASKBAR_HORIZONTAL_GAP;
   			var minimizedItemWidth:Number;
   			if (totalMinimizedItemWidth > availablePodWidth) // Items are too wide so resize.
   				minimizedItemWidth = Math.round((availablePodWidth - (len - 1) * TASKBAR_HORIZONTAL_GAP) / len);
   			else
   				minimizedItemWidth = TASKBAR_ITEM_WIDTH;
   			
   			for (i = 0; i < len; i++) {
   				pod = Pod(minimizedItems[i]);
   				pod.height = Pod.MINIMIZED_HEIGHT;
   				targetX = i * (TASKBAR_HORIZONTAL_GAP + minimizedItemWidth);
   				if (tween) {
   					addResizeAndMoveToParallel(pod, parallel, minimizedItemWidth, Pod.MINIMIZED_HEIGHT, targetX, minimizedPodY);
   				}
   				else {
   					pod.width = minimizedItemWidth;
   					pod.x = targetX;
   					pod.y = minimizedPodY;
   				}
   			}
   		}
   		
   		if (parallel != null && parallel.children.length > 0)
   			parallel.play();
   		
   		// Layout the drag highlight items.
   		len = dragHighlightItems.length;
   		for (i = 0; i < len; i++) {
   			var dragHighlight:DragHighlight = DragHighlight(dragHighlightItems[i]);
   			if (i > (items.length - 1)) {
   				dragHighlight.visible = false;
   				dragHighlight.x = 0;
   				dragHighlight.y = 0;
   				dragHighlight.width = 0;
   				dragHighlight.height = 0;
   			}
   			else {
   				var point:Point = Point(gridPoints[i]);
   				dragHighlight.x = point.x;
   				dragHighlight.y = point.y;
   				dragHighlight.width = itemWidth;
   				dragHighlight.height = itemHeight;
   				container.setChildIndex(dragHighlight, i); // Move the hightlights to the bottom of the z-index.
   			}
   		}
   	}
   	
   	// Creates a resize and move event and adds them to a parallel effect.
   	private function addResizeAndMoveToParallel(target:Pod, parallel:Parallel, 
                                                  widthTo:Number, heightTo:Number, xTo:Number, yTo:Number):void {
   		var resize:Resize = new Resize(target);
   		resize.widthTo = widthTo;
   		resize.heightTo = heightTo;
   		resize.easingFunction = Exponential.easeOut;
   		parallel.addChild(resize);
   		
   		var move:Move = new Move(target);
   		move.xTo = xTo;
   		move.yTo = yTo;
   		move.easingFunction = Exponential.easeOut;
   		parallel.addChild(move);
   	}
   	
   	// Returns the available width for all of the pods.
   	private function get availablePodWidth():Number {
   		return container.width - PADDING_RIGHT;
   	}
   	
   	// Returns the available height for all of the pods.
   	private function get availablePodHeight():Number {
   		return container.height - TASKBAR_HEIGHT - TASKBAR_PADDING_TOP;
   	}
   	
   	// Returns the available height for a maximized pod.
   	private function get availableMaximizedPodHeight():Number {
   		return container.height;
   	}
   	
   	// Returns the target y coord for a minimized pod.
   	private function get minimizedPodY():Number {
   		return container.height - TASKBAR_HEIGHT;
   	}
   }
}